// @Author EthanScriptOn
// @Desc
package config_source_loader

import (
	"errors"
	"fmt"
	"gitee.com/fatzeng/srf_switch_config/config_define"
	"gopkg.in/yaml.v2"
	"strconv"
	"strings"
)

type YamlFileSourceLoader struct {
	*FileBaseSourceLoader
}

func (y *YamlFileSourceLoader) GetConfigSourceType() config_define.ConfigSourceType {
	return config_define.YAML
}

func (y *YamlFileSourceLoader) Initialization() (err error) {
	keyValuePairsSlicing := GenerateSourceLoaderSlicing(":", true, 2)
	multiKeySlicing := GenerateSourceLoaderSlicing(".", true, -1)
	y.ConfigSourceLoaderSteps, err = GenerateAllowableCutFileBaseSourceLoaderSteps(y, keyValuePairsSlicing, multiKeySlicing, `(\\w+)\\[(\\d+)\\]`)
	if err != nil {
		return err
	}
	y.FileBaseSourceLoader, err = GenerateFileBaseSourceLoader(y, y.GetConfigSourceType())
	if err != nil {
		return err
	}
	return y.FileBaseSourceLoader.Initialization()
}

func (y *YamlFileSourceLoader) SplitSegment(describes []*SegmentLineDescribe) ([]Segment, error) {
	segmentsMap := make(map[string][]*SegmentLineDescribe)
	for _, describe := range describes {
		for _, keyDescribe := range describe.multiKeySlice {
			if keyDescribe.level == 0 {
				segmentKey := keyDescribe.key + "-" + strconv.Itoa(keyDescribe.index)
				lineDescribes, ok := segmentsMap[segmentKey]
				if !ok {
					lineDescribes = make([]*SegmentLineDescribe, 0)
				}
				lineDescribes = append(lineDescribes, describe)
				segmentsMap[segmentKey] = lineDescribes
			}
		}
	}
	segments := make([]Segment, 0)
	for _, lineDescribes := range segmentsMap {
		segments = append(segments, lineDescribes)
	}
	return segments, nil
}

func (y *YamlFileSourceLoader) ConvertLineDescribe(lines []string) ([]*SegmentLineDescribe, error) {
	lines, err := y.tileLine(lines)
	if err != nil {
		return nil, err
	}
	return y.ConfigSourceLoaderSteps.ConvertLineDescribe(lines)
}

func (y *YamlFileSourceLoader) IsAnnotation(line string) bool {
	return line == "" || strings.HasPrefix(line, "#")
}

func (y *YamlFileSourceLoader) tileLine(lines []string) ([]string, error) {
	yamlBuilder := strings.Builder{}
	for _, line := range lines {
		if y.IsAnnotation(line) {
			continue
		}
		if y.isAbbreviation(line) {
			return nil, errors.New("does not support abbreviation writing '{' ")
		}
		yamlBuilder.WriteString(line)
		yamlBuilder.WriteString("\n")
	}
	return y.yamlHandle(yamlBuilder.String())
}

func (y *YamlFileSourceLoader) yamlHandle(pendingData string) ([]string, error) {
	data := []byte(pendingData)
	var result interface{}
	err := yaml.Unmarshal(data, &result)
	if err != nil {
		return nil, err
	}
	return y.traverseYaml(result, ""), nil
}

func (y *YamlFileSourceLoader) traverseYaml(pendingNode interface{}, path string) []string {
	KeyValuePairs := make([]string, 0)
	switch segmentAttribute := pendingNode.(type) {
	case map[interface{}]interface{}:
		for key, value := range segmentAttribute {
			newPath := path
			if path != "" {
				newPath += "."
			}
			newPath += fmt.Sprintf("%v", key)
			y.traverseYaml(value, newPath)
		}
	case []interface{}:
		for index, value := range segmentAttribute {
			newPath := path
			if path != "" {
				newPath += "."
			}
			newPath = y.buildKeyNode(newPath, index)
			y.traverseYaml(value, newPath)
		}
	case string:
		KeyValuePairs = append(KeyValuePairs, y.buildKeyValPairs(path, segmentAttribute))
	default:
		KeyValuePairs = append(KeyValuePairs, y.buildKeyValPairs(path, fmt.Sprint(segmentAttribute)))
	}
	return KeyValuePairs
}

func (y *YamlFileSourceLoader) isAbbreviation(line string) bool {
	trimSpaceLine := strings.TrimSpace(line)
	return strings.HasPrefix(trimSpaceLine, "{")
}

func (y *YamlFileSourceLoader) buildKeyNode(key string, index int) string {
	return key + "[" + strconv.Itoa(index) + "]"
}

func (y *YamlFileSourceLoader) buildKeyValPairs(key, val string) string {
	return key + "=" + val
}
